package sysRecmd;

import main.SYRRES;
import core.Beer;
import core.SetBeer;
import core.SetReview;
import core.SetUser;
import core.User;

public class Recommandation {
	private SetUser trainUser;
	private SetBeer trainBeer;
	private SetUser testUser;
	private SetBeer testBeer;	
    
    private int noteID;
    private int numberOfConvergence;
    private int thresholdOfConvergence;
    
    // Nombre de features pour les utilisateurs et les produits
    private int F = 10;
    
    // Learning rate alpha = 0.01
    private double alpha = 0.01;
    // Regularisation rate beta = 0.02;
    private double beta = 0.02;
    // Biais rate delta = 1;
    private int delta = 1;

    private double[][] uT = new double[SYRRES.getUserSize()][F];
    private double[][] p = new double[SYRRES.getBeerSize()][F];
    
    // Les parametres pour le PERCEPTRON
    // Desired output
    private double z;
    // sum s
    private double s;
    // Error e
    private double e;

    public Recommandation(SetReview train, SetReview test, 
    						int noteID, int numberOfConvergence, int thresholdOfConvergence, 
    						boolean withRegularisation, boolean withBiais) {
    	trainUser = SYRRES.generateUsers(train.getReviews());
    	trainBeer = SYRRES.generateBeers(train.getReviews());
    	testUser = SYRRES.generateUsers(test.getReviews());
    	testBeer = SYRRES.generateBeers(test.getReviews());
        
    	this.noteID = noteID;
        this.numberOfConvergence = numberOfConvergence;
        this.thresholdOfConvergence = thresholdOfConvergence;

        if (withRegularisation) {
        	beta = 0.02;
        } else {
        	beta = 0;
        }
        
        if (withBiais) {
        	delta = 1;
        } else {
        	delta = 0;
        }        
        
        // Initialiser les features des utilisateurs et des produits aleatoire
        for (int k = 0; k < F; k++) {
        	for (int i = 0; i < SYRRES.getUserSize(); i++) {
        		if (trainUser.getUser(i).getBiereNote().size() > 0) {
        			uT[i][k] = Math.random() * 2 - 1;        	
        		} else {
        			uT[i][k] = 0;
        		}
        	}
        	for (int j = 0; j < SYRRES.getBeerSize(); j++) {
        		if (trainBeer.getBeer(j).getUserNote().size() > 0) {
        			p[j][k] = Math.random() * 2 - 1;
        		} else {
        			p[j][k] = 0;
        		}
        	}
        }    	
    }   
    
    /**
     * Faire la decomposition matricielle
     * @param loss : loss = 0 => SQUARE LOSS
     * 				 loss = 1 => HINGE LOSS 
     */
    public void doMatrixFactorization(int loss) {        
    	Beer beer;
		User user;
		
		// Step step
        int step = 0;
        double tmp1;
        double tmp2;
        
        while (true) {        	
        	for (int i = 0; i < trainUser.size(); i++) {
        		user = trainUser.getUser(i);
        		for (int j = 0; j < trainBeer.size(); j++) {
        			beer = trainBeer.getBeer(j);
        			if (user.hasBeer(beer.getId())) {
        				z = user.getNote(beer.getId(), noteID);
        	            
        				// Calculer le note prevu
        				s = 0;
        				for (int k = 0; k < F; k++) {
	                        s += uT[i][k] * p[j][k];
	                        
	                    }
        				s += delta * (user.getAverageNote(noteID) + beer.getAverageNote(noteID) - SYRRES.getAverageNote(noteID));
        				
        				// Erreur 
        				if (loss == SYRRES.SQUARE_LOSS) {
        					e = z - s;
        				} else if (loss == SYRRES.HINGE_LOSS) {
        					if (z * s >= 1) {
        						e = 0;
        					} else {
        						e = z * s;
        					}
        				}   
        				
        				for (int k = 0; k < F; k++) {
        	            	tmp1 = uT[i][k];
        	            	tmp2 = p[j][k];
//        	            	System.out.println("z " + z + " s "+ s + " e " + e + " uT[j][k] " + uT[i][k] + " p[j][k] " + p[j][k]);
        	            	uT[i][k] += alpha * (e * p[j][k] - beta * uT[i][k]);
        	            	p[j][k] += alpha * (e * tmp1 - beta * p[j][k]);
        	            	
        	            }
        			}
        		}
        	}                                  
            
        	if (checkConvergence(step, loss)) {
                System.out.println("done");
                break;
            }   
            step++;  
        }        
    }
    
    private boolean checkConvergence(int step, int loss) {
	   if (step == numberOfConvergence) {
		   return true;
	   } else {
		   if (evaluer(step, loss) < thresholdOfConvergence) {
			   return true;
		   } else {
			   return false;
		   }
	   }
    }
    
    private double evaluer(int step, int loss) {
    	double e = 0;
    	double numberOfGoodNote = 0;
    	double numberOfNote = 0;
    	
    	Beer beer;
		User user;
		
		for (int i = 0; i < testUser.size(); i++) {
    		user = testUser.getUser(i);
    		if (trainUser.getUser(i).getBiereNote().size() > 0) {
	    		for (int j = 0; j < testBeer.size(); j++) {
	    			beer = testBeer.getBeer(j);
	    			if (user.hasBeer(beer.getId())) {
	    				z = user.getNote(beer.getId(), noteID);
	    	            
	    				s = 0;
	    				for (int k = 0; k < F; k++) {
	                        s += uT[i][k] * p[j][k];
	                    }
	    				
	    				/* Partie d'evaluation */
	    				if (s != 0)		numberOfNote++;
	    				// Si le note reel et le note prevu sont aussi positve (ou negative) => bon note prevu
	    				if (z * s > 0)	numberOfGoodNote++; 
	    				/* ------------------- */
	    				
	    				if (loss == SYRRES.SQUARE_LOSS) {
	    					e += Math.pow(z - s, 2);
	    				} else if (loss == SYRRES.HINGE_LOSS) {
	    					e += Math.max(0, 1 - z * s);
	    				}
	    				
	    				for (int k = 0; k < F; k++) {
	    					e += beta * (Math.pow(uT[i][k], 2) + Math.pow(p[j][k], 2));
	    				}
	    				
	    				e += beta * delta * Math.pow(user.getAverageNote(noteID) - SYRRES.getAverageNote(noteID), 2);
	    				e += beta * delta * Math.pow(beer.getAverageNote(noteID) - SYRRES.getAverageNote(noteID), 2);
	    			}
	    		}
	    	}
		}
		System.err.println("Step " + step + " error " + e);
		System.out.println("Pourcentage des bons notes : " + ((float)numberOfGoodNote / numberOfNote * 100) + "%");		
    	return e;
    }
}
